const os = require('os');
const Events = require("events");
const ffmpeg = require('fluent-ffmpeg');

const Netease = require("./Netease");
const Bilibili = require("./Bilibili");
const KookVoice = require("./kookVoice");

class MusicCtrler {

    constructor(wsGetter, channelId, NeteaseConfig, BilibiliConfig, FfmpegPath, MusicPath) {

        if (os.platform() === 'linux') {
            ffmpeg.setFfmpegPath(FfmpegPath ?? '/usr/bin/ffmpeg');
        } else {
            ffmpeg.setFfmpegPath(FfmpegPath ?? './ffmpeg');
        }

        this.netease = new Netease(NeteaseConfig);
        this.bilibili = new Bilibili(BilibiliConfig);

        this.channelId = channelId;
        this.MUSIC_PATH = MusicPath ?? './res/music/';
        this.PlayList = [];
        this.randomPlayList = [];

        this.Mode = 1;
        this.MusicNow = {
            music: null,
            index: null,
        };
        this.playFlag = true;
        this.wsGetter = wsGetter;

        this.Emitter = new Events.EventEmitter();

        this.Emitter.on('NEW_MUSIC', async () => {
            if (this.Voice) return;
            this.playNextMusic()
                .catch(err => console.log(err));
        })

        this.Emitter.on('FINISH', async () => {
            if (this.Voice) delete this.Voice;
            if (!this.playFlag) return;
            this.playNextMusic()
                .catch(err => console.log(err));
        });

        this.randomPlayList.next = function () {

        }
    }

    async on(event, callback) {
        return this.Emitter.on(event, callback);
    }


    async refreshUrlByMusicData(musicData) {
        return new Promise((resolve, reject) => {
            if (!musicData || !musicData.music || !musicData.music.id) reject("NO_INPUT");
            this.netease.getMusicUrlById([musicData.music.id]).then((data) => {
                if (!data || !data[0] || !data[0].url) reject("NO_RESULT");
                musicData.music.url = data[0].url;
                resolve(musicData);
            }).catch(err => reject(err));
        });
    }

    // MusicNow: {id, name, artists, album, duration, VIP, url, gain}
    async addToPlayListByMusicName(name) {
        return new Promise(async (resolve, reject) => {
            this.netease.getMusicByName(name)
                .catch(err => reject(err))
                .then((data) => {
                    if (!data || !data[0] || !data[0].id) {
                        reject("NO_RESULT");
                        return;
                    }
                    this.getMusicById([data[0].id])
                        .catch(err => reject(err))
                        .then((res) => {
                            this.PlayList.splice((this.MusicNow.index ?? -1) + 1, 0, res[0]);
                            this.Emitter.emit("NEW_MUSIC");
                            this.Emitter.emit("UI_CHANGE");
                            resolve(res[0]);
                        });
                });
        });
    }
    // async addToPlayListByBilibiliBV(bv_id){
    // }

    async removeFromPlayListByMusicId(music_id) {
        for (let i = 0, len = this.PlayList.length; i < len; i++) {

            if (this.PlayList[i].id == music_id) {
                this.PlayList.splice(i, 1);
                this.Emitter.emit("UI_CHANGE");

                if (this.PlayList.length === 0) return this.stopPlay();
                if (i <= this.MusicNow.index) this.MusicNow.index--;

                if (this.MusicNow.music && this.MusicNow.music.id === music_id) {
                    return this.playNextMusic();
                };
                break;
            }
        }
    }

    // MusicNow: {id, name, artists, album, duration, VIP, url, gain}
    async importPlayListById(playlistId) {
        return new Promise((resolve, reject) => {
            this.netease.getMusicIdByPlaylist(playlistId)
                .then(async (musicIdArr) => {
                    this.getMusicById(musicIdArr)
                        .then(res => {
                            let ret = res[1];// ret: {id, name, artists, alia, duration, popularity, VIP}
                            for (let i = 0, len = res[0].length; i < len; i++) {
                                ret[i].url = res[0][i].url;
                                ret[i].gain = this.dBToMultipleGain(res[0][i].gain);
                            }

                            if (!this.PlayList) {
                                this.PlayList = res;
                            } else {
                                this.PlayList.splice(this.MusicNow.index + 1, 0, ...res);
                            }
                            this.Emitter.emit("NEW_MUSIC");
                            this.Emitter.emit("UI_CHANGE");

                            resolve(this.MusicNow.music);
                        })
                        .catch(err => { reject(err) });
                })
                .catch(err => reject(err));
        })
    }

    async clearPlayList() {
        this.stopPlay().catch(err => reject(err))
            .then(() => {
                this.PlayList = [];
                this.MusicNow = {
                    music: null,
                    index: null,
                };
                this.Emitter.emit("UI_CHANGE");
            })
    }

    async _getVoiceInstance() {
        return new Promise(async (resolve, reject) => {
            if (this.Voice) {
                await this.Voice.exitVoiceChannel()
                    .then(() => delete this.Voice)
                    .catch(err => reject(err));
            }
            this.wsGetter(this.channelId)
                .then((wsUrl) => {
                    KookVoice.createInstance(wsUrl)
                        .then(resInstance => {
                            resolve(resInstance);
                        })
                        .catch(err => reject(err));
                })
                .catch(err => reject(err));

        });

    }

    changePlayMode(mode) {
        this.Mode = mode ?? (this.Mode + 1) % 4;
        return this.Mode;
    }

    shufflePlayList() {
        for (let i = this.PlayList.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [this.PlayList[i], this.PlayList[j]] = [this.PlayList[j], this.PlayList[i]];
        }
        this.Emitter.emit("UI_CHANGE");
    }

    async stopPlay() {
        this.playFlag = false;
        await this.stopStream().catch(err => console.log(err));
        return new Promise((resolve, reject) => {
            if (this.Voice) {
                this.Voice.exitVoiceChannel()
                    .then(() => { delete this.Voice; resolve() })
                    .catch(err => reject(err));
            } else {
                resolve();
            }
        })
    }

    async startPlay() {
        this.playFlag = true;
        this.playNextMusic()
            .catch(err => console.log(err));
    }

    async playNextMusic() {
        return new Promise((resolve, reject) => {
            this.stopStream().catch(err => reject(err));
            switch (this.Mode) {
                case 1: // loop
                    if (this.MusicNow.index >= this.PlayList.length - 1) this.MusicNow.index = -1;
                    this.MusicNow.index += 1;
                    this.MusicNow.music = this.PlayList[this.MusicNow.index];
                    break;
                case 2: // random
                    break;
                case 3: // single
                    break;
                default:// order
                    if (++this.MusicNow.index >= this.PlayList.length) {
                        this.Playing = false;
                    }
                    this.MusicNow.music = this.randomPlayList[this.MusicNow.index];
                    break;
            }
            this.playMusicByMusicNow().then((res) => resolve(res)).catch(err => reject(err));
        });
    }

    async playPrevMusic() {
        return new Promise((resolve, reject) => {
            this.stopStream().catch(err => reject(err));
            switch (this.Mode) {
                case 1: // loop
                    if (this.MusicNow.index <= 0) this.MusicNow.index = this.PlayList.length;
                    this.MusicNow.index -= 1;
                    this.MusicNow.music = this.PlayList[this.MusicNow.index];
                    break;
                case 2: // random
                    break;
                case 3: // single
                    break;
                default:// order
                    if (--this.MusicNow.index <= this.PlayList.length) {
                        this.Playing = false;
                    }
                    this.MusicNow.music = this.randomPlayList[this.MusicNow.index];
                    break;
            }
            this.playMusicByMusicNow().then((res) => resolve(res)).catch(err => reject(err));
        });
    }

    async playMusicByMusicNow() {
        return new Promise((resolve, reject) => {
            this.refreshUrlByMusicData(this.MusicNow)
                .then(async () => {
                    this._getVoiceInstance()
                        .then((resInstance) => {
                            this.playFlag = true;
                            this.Voice = resInstance;
                            this.streamMusicNoParams(this.MusicNow.music);
                            this.Emitter.emit("UI_CHANGE");
                            this.Emitter.emit("PLAYING_CHANGE");
                            resolve(this.MusicNow.music);
                        })
                        .catch(err => reject(err));
                })
                .catch(err => reject(err));
        })
    }

    async getMusicById(musicIdArr) {
        return new Promise((resolve, reject) => {
            let ret = null;

            let handler = (res) => {
                if (!ret) ret = res; // {id, name, artists, album, duration, popularity, VIP}
                else {
                    for (let i = 0, len = ret.length; i < len; i++)
                        ret[i] = { ...ret[i], ...res[i] };
                    resolve(ret);
                };
            };

            this.netease.getMusicUrlById(musicIdArr)
                .catch(err => reject(err))
                .then(async res => {
                    handler(res);
                });
            this.netease.getMusicDetailById(musicIdArr)
                .catch(err => reject(err))
                .then(async res => {
                    handler(res);
                });
        })
    }


    async streamMusicNoParams(music) {
        const musicUrl = music.url;
        const musicGain = music.VIP ? 0.1 : music.gain;

        this.startStream(musicUrl, musicGain)
            .then(() => this.Emitter.emit("finished"))
            .catch(err => {
                if (err.message === "ffmpeg was killed with signal SIGKILL") {
                    console.log("[VOICE] FFmpeg Killed!");
                    this.Emitter.emit("stopped")
                }
                else {
                    if (this.stream) this.stopStream;
                    console.log(err);
                }
            });
    }

    async streamMusicWithParams(musicUrl, musicGain, params) {
        return new Promise((resolve, reject) => {
            this.getMusicWithParams(musicUrl, outputPath, params)
                .then(path => this.startStream(path, musicGain, null, null, resolve()))
                .catch(err => reject(err));
        });
    }

    async playMusicByIndex(index) {
        this.MusicNow.index = index;
        this.MusicNow.music = this.PlayList[index];
        return this.MusicNow;

    }

    async startStream(path, gain, on_open) {
        return new Promise((resolve, reject) => {
            if (!path) throw new Error("NO_PATH");
            gain = gain ?? 0.05;
            this.stream = new ffmpeg()
                .input(path)
                .inputOptions(['-re'])
                .audioFilters(`volume=${gain}`)
                .outputOptions([
                    '-map 0:a:0',
                    '-bufsize 64k',
                    '-threads 4'
                ])
                .audioCodec('libopus')
                .audioBitrate('320k')
                .audioChannels(2)
                .audioFrequency(48000)
                .outputFormat('tee')
                .removeAllListeners('start')
                .removeAllListeners('error')
                .removeAllListeners('end');

            this.stream.on('start', (commandLine) => {
                console.log('[VOICE] startStream');
                console.log('Spawned Ffmpeg with command: ' + commandLine);
                if (on_open) on_open;
            });
            this.stream.on('error', async (e) => {
                reject(e);
            });
            this.stream.on('end', async (e) => {
                console.log('[VOICE] stream end' + e);
                await this.Voice.exitVoiceChannel();
                await this.stopStream();
                this.Emitter.emit("FINISH");
                resolve();
            });
            this.stream.save(`[select=a:f=rtp:ssrc=${this.Voice.ssrc}:payload_type=111]${this.Voice.rtpUrl}`);

        })
    }

    async stopStream() {
        if (this.stream) {
            await this.stream.kill('SIGKILL');
            delete this.stream;
            console.log('[VOICE] stream stopped');
        }
    }

    // async getMusicWithParams(input_path, output_path, paramArr){
    //     return new Promise((resolve, reject)=>{
    //         const filter = 1;
    //         const converter = this.ffmpeg()
    //         .input(input_path)
    //         .audioFilters(filter)
    //         .outputOptions([
    //             '-filter:a',
    //             '-map 0:a:0',
    //             '-preset veryfast',
    //             '-threads 4'
    //           ])
    //         .audioCodec('libopus')
    //         .audioBitrate('320k')
    //         .audioChannels(2)
    //         .audioFrequency(48000)
    //         .outputFormat('opus')
    //         .removeAllListeners('start')
    //         .removeAllListeners('error')
    //         .removeAllListeners('end')
    //         .on('start', (commandLine) => {
    //             console.log('[VOICE] startStream');
    //             console.log('Spawned Ffmpeg with command: ' + commandLine);
    //         })
    //         .on('error', (e) => {
    //             console.log('[VOICE] stream Err' + e);
    //             reject(e);
    //         })
    //         .on('end', (e) => {
    //             console.log('[VOICE] stream End' + e);
    //             resolve();
    //         });

    //     converter.save(output_path);
    //     })

    // };

}

module.exports = MusicCtrler;